<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <title>直播互动平台</title>
  <!-- 引入图标库增强视觉 -->
  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.14/dist/hls.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js"></script>
  <style>
    :root {
      --bg: #0b0f1a;
      --card: #12182a;
      --card-hover: #161e35;
      --muted: #8aa0c8;
      --accent: #4f8cff;
      --accent-hover: #3a79e9;
      --ok: #22c55e;
      --ok-hover: #1db954;
      --danger: #ef4444;
      --danger-hover: #dc2626;
      --border: rgba(255, 255, 255, 0.08);
      --border-hover: rgba(255, 255, 255, 0.15);
      --shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
      --shadow-hover: 0 12px 40px rgba(0, 0, 0, 0.3);
      --transition: all 0.25s ease;
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      background: var(--bg);
      color: #e7eefc;
      font-family: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
      line-height: 1.5;
    }

    /* 布局容器 */
    .container {
      display: grid;
      grid-template-columns: 1.4fr 1fr;
      gap: 16px;
      padding: 16px;
      min-height: 100vh;
    }

    /* 卡片组件 */
    .card {
      background: var(--card);
      border-radius: 16px;
      box-shadow: var(--shadow);
      overflow: hidden;
      transition: var(--transition);
    }

    .card:hover {
      box-shadow: var(--shadow-hover);
    }

    /* 头部区域 */
    .header {
      padding: 12px 14px;
      border-bottom: 1px solid var(--border);
      display: flex;
      gap: 10px;
      align-items: center;
      flex-wrap: wrap;
      background: var(--card-hover);
    }

    /* 输入框与选择器 */
    .header input,
    .header select,
    .chat-input input,
    .chat-input textarea {
      background: #0d1323;
      color: #dce7ff;
      border: 1px solid var(--border);
      border-radius: 10px;
      padding: 10px 12px;
      outline: none;
      flex: 1;
      min-width: 80px;
      transition: var(--transition);
      font-size: 14px;
    }

    .header input:focus,
    .header select:focus,
    .chat-input input:focus,
    .chat-input textarea:focus {
      border-color: var(--accent);
      box-shadow: 0 0 0 2px rgba(79, 140, 255, 0.2);
    }

    /* 按钮样式 */
    .btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 6px;
      border: none;
      padding: 10px 14px;
      border-radius: 12px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      transition: var(--transition);
    }

    .btn-primary {
      background: var(--accent);
      color: #fff;
    }

    .btn-primary:hover:not(:disabled) {
      background: var(--accent-hover);
    }

    .btn-danger {
      background: var(--danger);
      color: #fff;
    }

    .btn-danger:hover:not(:disabled) {
      background: var(--danger-hover);
    }

    .btn:disabled {
      opacity: 0.6;
      cursor: not-allowed;
      transform: none;
    }

    /* 播放器区域 */
    .player-wrap {
      position: relative;
      background: #000;
      aspect-ratio: 16/9;
      overflow: hidden;
      border-radius: 8px 8px 0 0;
    }

    video {
      width: 100%;
      height: 100%;
      background: #000;
      object-fit: cover;
    }

    /* 状态条 */
    .status-bar {
      position: absolute;
      top: 10px;
      left: 10px;
      background: rgba(0, 0, 0, 0.6);
      backdrop-filter: blur(4px);
      padding: 6px 12px;
      border-radius: 10px;
      font-size: 12px;
      color: #fff;
      display: flex;
      align-items: center;
      gap: 6px;
      z-index: 10;
    }

    .status-bar::before {
      content: "";
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--danger);
      animation: pulse 1.5s infinite;
    }

    .status-playing::before {
      background: var(--ok);
    }

    /* 弹幕层 */
    .danmu-layer {
      pointer-events: none;
      position: absolute;
      inset: 0;
      overflow: hidden;
      z-index: 5;
    }

    /* 侧边栏 */
    .side {
      display: flex;
      flex-direction: column;
      height: 100%;
    }

    /* 聊天列表 */
    .chat-list {
      flex: 1;
      overflow: auto;
      padding: 12px;
      scrollbar-width: thin;
      scrollbar-color: var(--accent) transparent;
    }

    .chat-list::-webkit-scrollbar {
      width: 6px;
    }

    .chat-list::-webkit-scrollbar-thumb {
      background: var(--accent);
      border-radius: 3px;
    }

    .chat-item {
      margin-bottom: 12px;
      line-height: 1.4;
      padding: 6px 0;
      transition: var(--transition);
    }

    .chat-item:hover {
      padding-left: 4px;
    }

    .chat-item .nick {
      color: #7cd1ff;
      font-weight: 500;
      margin-right: 6px;
    }

    .chat-item .time {
      color: var(--muted);
      font-size: 12px;
      margin-left: 6px;
    }

    .chat-item.system {
      color: var(--muted);
      font-style: italic;
    }

    /* 聊天输入区 */
    .chat-input {
      display: grid;
      grid-template-columns: 1fr auto;
      gap: 8px;
      padding: 12px;
      border-top: 1px solid var(--border);
      background: var(--card-hover);
    }

    .chat-input textarea {
      resize: none;
      min-height: 60px;
    }

    /* 工具栏 */
    .toolbar {
      display: flex;
      gap: 8px;
      align-items: center;
      flex-wrap: wrap;
    }

    /* 复选框美化 */
    .checkbox-wrap {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 13px;
      color: #dce7ff;
      cursor: pointer;
    }

    .checkbox-wrap input {
      width: 14px;
      height: 14px;
      accent-color: var(--accent);
    }

    /* 提示文本 */
    .tip {
      color: var(--muted);
      font-size: 12px;
      padding: 8px 12px;
      background: rgba(0, 0, 0, 0.1);
      border-top: 1px solid var(--border);
    }

    .tip .bad {
      color: var(--danger);
    }

    /* 动画 */
    @keyframes pulse {
      0%, 100% {
        opacity: 0.7;
        transform: scale(0.9);
      }
      50% {
        opacity: 1;
        transform: scale(1.1);
      }
    }

    /* 响应式调整 */
    @media (max-width: 980px) {
      .container {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto;
      }

      .header {
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
      }

      .chat-input {
        grid-template-columns: 1fr;
      }

      .toolbar {
        justify-content: flex-end;
      }
    }

    @media (max-width: 480px) {
      .container {
        padding: 8px;
      }

      .header input,
      .header select {
        padding: 8px 10px;
        font-size: 13px;
      }

      .btn {
        padding: 8px 10px;
        font-size: 13px;
      }

      .chat-input input,
      .chat-input textarea {
        padding: 8px 10px;
        font-size: 13px;
      }

      .tip {
        padding: 6px 8px;
        font-size: 11px;
      }
    }
  </style>
</head>
<body>
<div class="container">
  <!-- 直播播放区 -->
  <section class="card">
    <div class="header">
      <input id="streamUrl" type="text" placeholder="直播地址（HLS .m3u8 或 FLV .flv）" />
      <select id="protocol">
        <option value="auto">自动识别</option>
        <option value="hls">HLS (.m3u8)</option>
        <option value="flv">HTTP-FLV (.flv)</option>
      </select>
      <button id="btnPlay" class="btn btn-primary">
        <i class="fa fa-play" aria-hidden="true"></i> 开始播放
      </button>
      <button id="btnStop" class="btn btn-danger" disabled>
        <i class="fa fa-stop" aria-hidden="true"></i> 停止
      </button>
    </div>
    <div class="player-wrap">
      <video id="video" controls playsinline webkit-playsinline></video>
      <div class="status-bar" id="playStatus">未播放</div>
      <div class="danmu-layer" id="danmu"></div>
    </div>
    <div class="tip">
      <i class="fa fa-info-circle" aria-hidden="true"></i> Safari/移动端原生支持 HLS；Chrome 桌面端可用 hls.js。FLV 需开启 CORS 跨域设置。
    </div>
  </section>

  <!-- 聊天互动区 -->
  <aside class="card side">
    <div class="header">
      <input id="nickname" type="text" placeholder="昵称（必填）" />
      <input id="room" type="text" placeholder="房间号（如 live001）" />
      <input id="wsUrl" type="text" placeholder="聊天服务器 ws(s)://host:port/chat" style="flex: 1" />
      <button id="btnConnect" class="btn btn-primary">
        <i class="fa fa-plug" aria-hidden="true"></i> 连接
      </button>
      <button id="btnDisconnect" class="btn btn-danger" disabled>
        <i class="fa fa-unplug" aria-hidden="true"></i> 断开
      </button>
    </div>

    <div id="chatList" class="chat-list"></div>

    <div class="chat-input">
      <textarea id="msgInput" rows="2" placeholder="输入消息，Enter 发送，Shift+Enter 换行"></textarea>
      <div class="toolbar">
        <label class="checkbox-wrap">
          <input id="sendDanmu" type="checkbox" checked /> 同步弹幕
        </label>
        <button id="btnSend" class="btn btn-primary">
          <i class="fa fa-paper-plane" aria-hidden="true"></i> 发送
        </button>
      </div>
    </div>
  </aside>
</div>

<script>
  // 工具函数：简化DOM选择
  const $ = (id) => document.getElementById(id);
  
  // 核心元素引用
  const video = $("video");
  const playStatus = $("playStatus");
  let hls, flvPlayer;

  /**
   * 设置播放状态
   * @param {string} text - 状态文本
   * @param {boolean} isPlaying - 是否正在播放
   */
  function setStatus(text, isPlaying = false) {
    playStatus.textContent = text;
    playStatus.className = `status-bar ${isPlaying ? 'status-playing' : ''}`;
  }

  /**
   * 销毁所有播放器实例
   */
  function destroyPlayers() {
    if (hls) {
      hls.destroy();
      hls = null;
    }
    if (flvPlayer) {
      try {
        flvPlayer.unload();
        flvPlayer.detachMediaElement();
        flvPlayer.destroy();
      } catch (e) {
        console.warn('销毁FLV播放器异常:', e);
      } finally {
        flvPlayer = null;
      }
    }
    video.removeAttribute('src');
    video.load();
  }

  /**
   * 播放直播流
   */
  async function play() {
    const url = $("streamUrl").value.trim();
    const mode = $("protocol").value;

    if (!url) {
      return alert("请输入直播地址");
    }

    // 更新按钮状态
    setStatus("连接中...");
    $("btnPlay").disabled = true;
    $("btnStop").disabled = false;
    destroyPlayers();

    // 识别流类型
    const isM3U8 = url.endsWith('.m3u8') || url.includes('m3u8');
    const isFLV = url.endsWith('.flv') || url.includes('.flv');

    try {
      // HLS播放逻辑
      if (mode === 'hls' || (mode === 'auto' && isM3U8)) {
        // 原生HLS支持（Safari）
        if (video.canPlayType('application/vnd.apple.mpegurl')) {
          video.src = url;
          await video.play();
          setStatus('HLS 原生播放中', true);
          return;
        }
        // HLS.js播放（其他浏览器）
        if (window.Hls && Hls.isSupported()) {
          hls = new Hls({
            maxBufferLength: 10,
            liveSyncDuration: 3,
            enableWorker: true,
            lowLatencyMode: true
          });
          hls.loadSource(url);
          hls.attachMedia(video);
          
          hls.on(Hls.Events.MANIFEST_PARSED, () => {
            video.play().catch(e => console.warn('HLS自动播放失败:', e));
          });
          hls.on(Hls.Events.ERROR, (_, data) => {
            setStatus(`HLS 错误：${data?.details || '未知错误'}`);
          });
          setStatus('HLS.js 播放中', true);
          return;
        }
      }

      // FLV播放逻辑
      if (mode === 'flv' || (mode === 'auto' && isFLV)) {
        if (window.flvjs && flvjs.isSupported()) {
          flvPlayer = flvjs.createPlayer({
            type: 'flv',
            url,
            isLive: true,
            cors: true,
            enableStashBuffer: false
          });
          flvPlayer.attachMediaElement(video);
          flvPlayer.load();
          video.play().catch(e => console.warn('FLV自动播放失败:', e));
          setStatus('FLV.js 播放中', true);
          return;
        }
      }

      // 通用播放逻辑
      video.src = url;
      await video.play();
      setStatus('直接播放中', true);

    } catch (e) {
      console.error('播放失败:', e);
      setStatus('播放失败');
      alert(`播放失败：${e.message || '未知错误'}`);
      // 恢复按钮状态
      $("btnPlay").disabled = false;
      $("btnStop").disabled = true;
    }
  }

  /**
   * 停止播放
   */
  function stop() {
    destroyPlayers();
    setStatus('未播放');
    $("btnPlay").disabled = false;
    $("btnStop").disabled = true;
  }

  // 播放/停止按钮事件绑定
  $("btnPlay").addEventListener('click', play);
  $("btnStop").addEventListener('click', stop);

  /**
   * 发送弹幕
   * @param {string} text - 弹幕内容
   */
  function pushDanmu(text) {
    const danmuLayer = $("danmu");
    if (!danmuLayer) return;

    const span = document.createElement('span');
    span.textContent = text;
    span.style.cssText = `
      position: absolute;
      white-space: nowrap;
      left: ${danmuLayer.clientWidth}px;
      top: ${Math.max(10, Math.random() * (danmuLayer.clientHeight - 30))}px;
      font-weight: 600;
      text-shadow: 0 2px 6px rgba(0,0,0,.8);
      color: ${getRandomColor()};
      opacity: 0;
      animation: fadeIn 0.3s forwards;
    `;
    danmuLayer.appendChild(span);

    // 弹幕速度（随机变化）
    const speed = 60 + Math.random() * 90;
    const duration = (danmuLayer.clientWidth + span.clientWidth) / speed;
    
    // 弹幕动画
    span.style.animation = `
      fadeIn 0.3s forwards,
      moveLeft ${duration}s linear forwards
    `;

    // 动画结束后移除元素
    setTimeout(() => {
      if (danmuLayer.contains(span)) {
        span.style.animation = 'fadeOut 0.5s forwards';
        setTimeout(() => danmuLayer.removeChild(span), 500);
      }
    }, duration * 1000);
  }

  /**
   * 获取随机弹幕颜色（亮色为主）
   */
  function getRandomColor() {
    const colors = [
      '#ffffff', '#ffd700', '#7fff00', '#00ffff', '#ff69b4',
      '#ffa500', '#98fb98', '#87cefa', '#d8bfd8', '#f0e68c'
    ];
    return colors[Math.floor(Math.random() * colors.length)];
  }

  // 添加弹幕动画样式
  const style = document.createElement('style');
  style.textContent = `
    @keyframes moveLeft {
      from { transform: translateX(0); }
      to { transform: translateX(-100%); }
    }
    @keyframes fadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }
    @keyframes fadeOut {
      from { opacity: 1; }
      to { opacity: 0; }
    }
  `;
  document.head.appendChild(style);

  // ------------------------------
  // 聊天功能相关逻辑
  // ------------------------------
  let ws;
  const chatList = $("chatList");

  /**
   * 获取当前时间（格式化）
   */
  function getCurrentTime() {
    const d = new Date();
    return d.toLocaleTimeString('zh-CN', {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit'
    });
  }

  /**
   * 追加聊天消息
   * @param {Object} msg - 消息对象
   * @param {string} msg.nick - 昵称
   * @param {string} msg.text - 内容
   * @param {boolean} msg.system - 是否系统消息
   */
  function appendMsg({ nick, text, system = false }) {
    if (!chatList) return;

    const div = document.createElement('div');
    div.className = `chat-item ${system ? 'system' : ''}`;
    
    const time = getCurrentTime();
    if (system) {
      div.innerHTML = `<span class="time">[${time}]</span> <i class="fa fa-bell-o" aria-hidden="true"></i> ${text}`;
    } else {
      div.innerHTML = `
        <span class="nick">${escapeHtml(nick)}</span>
        <span>${escapeHtml(text)}</span>
        <span class="time">[${time}]</span>
      `;
    }

    // 消息动画
    div.style.opacity = '0';
    div.style.transform = 'translateY(8px)';
    chatList.appendChild(div);
    
    // 触发重排后执行动画
    setTimeout(() => {
      div.style.transition = 'all 0.3s ease';
      div.style.opacity = '1';
      div.style.transform = 'translateY(0)';
    }, 10);

    // 滚动到底部
    chatList.scrollTop = chatList.scrollHeight;
  }

  /**
   * HTML转义（防XSS）
   */
  function escapeHtml(str) {
    if (!str) return '';
    return str.replace(/[&<>"']/g, c => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    }[c]));
  }

  /**
   * 连接聊天服务器
   */
  function connectChat() {
    const wsUrl = $("wsUrl").value.trim();
    const nick = $("nickname").value.trim();
    const room = $("room").value.trim() || 'public';

    if (!wsUrl || !nick) {
      return alert('请填写聊天服务器地址与昵称');
    }

    // 关闭现有连接
    if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
      ws.close();
    }

    // 构建带参数的WS地址
    const urlObj = new URL(wsUrl);
    urlObj.searchParams.set('room', room);
    urlObj.searchParams.set('nick', encodeURIComponent(nick));

    // 创建新连接
    ws = new WebSocket(urlObj.toString());

    ws.onopen = () => {
      appendMsg({
        system: true,
        text: `已连接到房间「${room}」`
      });
      $("btnConnect").disabled = true;
      $("btnDisconnect").disabled = false;
    };

    ws.onmessage = (ev) => {
      try {
        const data = JSON.parse(ev.data);
        if (data.type === 'msg') {
          appendMsg({
            nick: data.nick || '未知用户',
            text: data.text
          });
          // 同步发送弹幕
          if ($("sendDanmu").checked) {
            pushDanmu(`${data.nick || '未知用户'}: ${data.text}`);
          }
        } else if (data.type === 'system') {
          appendMsg({
            system: true,
            text: data.text
          });
        }
      } catch (e) {
        // 处理非JSON格式消息
        appendMsg({
          nick: '系统',
          text: `收到未知消息: ${escapeHtml(ev.data)}`
        });
      }
    };

    ws.onclose = (ev) => {
      appendMsg({
        system: true,
        text: ev.wasClean ? '已正常断开连接' : '连接意外断开'
      });
      $("btnConnect").disabled = false;
      $("btnDisconnect").disabled = true;
    };

    ws.onerror = (err) => {
      appendMsg({
        system: true,
        text: `连接出错：${err.message || '未知错误'}`
      });
    };
  }

  /**
   * 断开聊天连接
   */
  function disconnectChat() {
    if (ws) {
      ws.close(1000, '用户主动断开');
      ws = null;
    }
  }

  /**
   * 发送聊天消息
   */
  function sendChatMsg() {
    const text = $("msgInput").value.trim();
    const nick = $("nickname").value.trim() || '游客';

    if (!text) return;

    // 检查连接状态
    if (!ws || ws.readyState !== WebSocket.OPEN) {
      appendMsg({
        system: true,
        text: '未连接聊天服务器，请先连接'
      });
      return;
    }

    // 发送消息
    const payload = JSON.stringify({
      type: 'msg',
      text,
      timestamp: Date.now()
    });
    ws.send(payload);

    // 清空输入框
    $("msgInput").value = '';

    // 本地同步显示（无需等待服务器回执）
    if ($("sendDanmu").checked) {
      pushDanmu(`${nick}: ${text}`);
    }
  }

  // 聊天相关事件绑定
  $("btnConnect").addEventListener('click', connectChat);
  $("btnDisconnect").addEventListener('click', disconnectChat);
  $("btnSend").addEventListener('click', sendChatMsg);
  $("msgInput").addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendChatMsg();
    }
  });

  // 窗口关闭时清理资源
  window.addEventListener('beforeunload', () => {
    destroyPlayers();
    disconnectChat();
  });
</script>
</body>
</html>